#include "portal_pkt.h"
#include "portal_user.h"
#include "portal_http.h"
#include "portal_md5.h"

static void portal_print_pkt(uchar *buf, int len)
{
    int i;

    if (!portal_debug) {
        return;
    }

    for (i = 0; i < len; i++) {
        if (i % 16 == 0) {
            printf("\n");
        }
        printf("%02x ", buf[i]);
    }
    printf("\n");
}

static void portal_parse_attr(struct portal_user_s *user, uchar *attr, int attr_num)
{
    int i;
    struct tlv_s *tlv;

    for (i = 0; i < attr_num; i++) {
        tlv = (struct tlv_s *)attr;
        switch (tlv->type) {
        case PORTAL_ATTR_CHALLENGE:
            memcpy(user->challenge, tlv->value, CHALLENGE_LEN);
            break;
        case PORTAL_ATTR_ERR_ID:
            memset(user->err_id, 0, sizeof(user->err_id));
            memcpy(user->err_id, tlv->value, tlv->len);
            break;
        case PORTAL_ATTR_SESSIONID:
            memcpy(user->mac, tlv->value, 6);
            break;
        case PORTAL_ATTR_BASIP:
            memcpy(&user->nasip, tlv->value, sizeof(IPADDRTYPE));
            break;
        case PORTAL_ATTR_NASID:
            break;
        case PORTAL_ATTR_SESSION_START_TIME:
            break;
        case PORTAL_ATTR_SESSION_STOP_TIME:
            break;
        case PORTAL_ATTR_SESSION_TIME:
            break;
        case PORTAL_ATTR_USER_AGENT:
            break;
        case PORTAL_ATTR_INPUT_OCTETS:
            break;
        case PORTAL_ATTR_OUTPUT_OCTETS:
            break;
        case PORTAL_ATTR_INPUT_PACKETS:
            break;
        case PORTAL_ATTR_OUTPUT_PACKETS:
            break;
        case PORTAL_ATTR_INPUT_GIGAWORDS:
            break;
        case PORTAL_ATTR_OUTPUT_GIGAWORDS:
            break;
        case PORTAL_ATTR_USER_IPV6:
            memcpy(&user->ipv6, tlv->value, sizeof(IPADDRTYPE6));
            break;
        default:
            break;
        }
        attr += tlv->len;
    }
}

static void portal_parse_pkt(int fd, struct portal_pkt_s *pkt)
{
    IPADDRTYPE ipv4;
    int http_fd;
    struct portal_user_s *user;
    uchar challenge[CHALLENGE_LEN];
    char send_buf[2048];
    char ipbuf[64];
    struct portal_user_s temp_user;

    if (pkt->ver == 0x2 && pkt->userip) {
        return;
    }

    memset(challenge, 0, CHALLENGE_LEN);
    memset(&temp_user, 0, sizeof(temp_user));
    portal_parse_attr(&temp_user, pkt->attrs, pkt->attrnum);
    if (pkt->ver == 0x01) {
        ipv4 = ntohl(pkt->userip);
        user = portal_find_user(ipv4, NULL, AF_INET);
        if (!user) {
            if (!ipv4 && pkt->type == PORTAL_TYPE_NTF_LOGOUT) {

            } else if (ipv4 && pkt->type == PORTAL_TYPE_MAC_INFO_REQ) {
                user = malloc(sizeof(struct portal_user_s));
                if (!user) {
                    return;
                }
                memset(user, 0, sizeof(struct portal_user_s));
                user->authen_mode = pkt->pap_chap;
                user->ipv4 = ipv4;
                memcpy(user->mac, temp_user.mac, 6);
                user->nasip = temp_user.nasip;
                PORTAL_DEBUG("add user %x, nasip %x", user->ipv4, user->nasip);
                list_add_tail(&user->lst, &portal_args.v4_user);
                user->af = AF_INET;
            } else {
                return;
            }
        }

        switch (pkt->type) {
        case PORTAL_TYPE_ACK_CHALLENGE:
            if (pkt->errcode == 0x01 || pkt->errcode == 0x04) {
                snprintf(send_buf, sizeof(send_buf), portal_result_authen_failed, user->err_id);
            http_fd = portal_get_authen_fd(&user->sock_lst);
            if (http_fd) {
                send(http_fd, send_buf, strlen(send_buf), 0);
            }
            http_fd = portal_get_authen_fd(&user->sock_lst);
            if (http_fd) {
                send(http_fd, send_buf, strlen(send_buf), 0);
            }

                portal_del_user(user);
                PORTAL_DEBUG("here");
                return;
            } else if (pkt->errcode == 0x3) {
                break;
            }

            memcpy(user->challenge, temp_user.challenge, CHALLENGE_LEN);
            if (!memcmp(user->challenge, challenge, CHALLENGE_LEN)) {
                return;
            }
            user->req_id = ntohs(pkt->reqid);
            portal_send_msg(user, fd, PORTAL_TYPE_REQ_AUTH);
            break;
        case PORTAL_TYPE_ACK_AUTH:
            if (pkt->errcode == 0x01 || pkt->errcode == 0x04) {
                memcpy(user->err_id, temp_user.err_id, sizeof(user->err_id));
                snprintf(send_buf, sizeof(send_buf), portal_result_authen_failed, user->err_id);
                http_fd = portal_get_authen_fd(&user->sock_lst);
                if (http_fd) {
                    send(http_fd, send_buf, strlen(send_buf), 0);
                }
                portal_del_user(user);
                return;
            } else if (pkt->errcode == 0x03) {
                break;
            }
            portal_send_msg(user, fd, PORTAL_TYPE_AFF_ACK_AUTH);
            portal_bindmac_upd_userinfo(user);
            PORTAL_DEBUG("bind user %d, nasip %x", user->bind_mac, user->nasip);
            if (!user->bind_mac) {
                inet_ntop(AF_INET, &portal_args.portal_ip, ipbuf, sizeof(ipbuf));
                snprintf(send_buf, sizeof(send_buf), portal_result_302, ipbuf, portal_args.http_port, ONLINE, user->sessionid);
                http_fd = portal_get_authen_fd(&user->sock_lst);
                if (http_fd) {
                    send(http_fd, send_buf, strlen(send_buf), 0);
                }
            }
            break;
        case PORTAL_TYPE_ACK_LOGOUT:
            inet_ntop(AF_INET, &portal_args.portal_ip, ipbuf, sizeof(ipbuf));
            snprintf(send_buf, sizeof(send_buf), portal_result_302, ipbuf, portal_args.http_port, OFFLINE, user->sessionid);
            http_fd = portal_get_authen_fd(&user->sock_lst);
            if (http_fd) {
                send(http_fd, send_buf, strlen(send_buf), 0);
            }
            break;
        case PORTAL_TYPE_NTF_LOGOUT:
            portal_send_msg(user, fd, PORTAL_TYPE_ACK_LOGOUT);
            portal_del_user(user);
            break;
        case PORTAL_TYPE_ACK_INFO:
            break;
        case PORTAL_TYPE_MAC_INFO_REQ:
            if (portal_find_bindmac(user->mac)) {
                user->bind_mac = TRUE;
                PORTAL_DEBUG("here");
            }

            PORTAL_DEBUG("shuffle %d, user mac %d", portal_args.portal_mac_sms_shuffle, user->bind_mac);
            if (portal_args.portal_mac_sms_shuffle && user->bind_mac) {
                portal_bindmac_get_userinfo(user);
                portal_send_msg(user, fd, user->authen_mode ? PORTAL_TYPE_REQ_AUTH : PORTAL_TYPE_REQ_CHALLENGE);
                portal_send_msg(user, fd, PORTAL_TYPE_MAC_INFO_ACK);
            } else {
                portal_send_msg(user, fd, PORTAL_TYPE_MAC_INFO_ACK);
                if (user->bind_mac) {
                    portal_bindmac_get_userinfo(user);
                    portal_send_msg(user, fd, user->authen_mode ? PORTAL_TYPE_REQ_AUTH : PORTAL_TYPE_REQ_CHALLENGE);
                }
            }
            break;
        case PORTAL_TYPE_USER_ONLINE:
            PORTAL_DEBUG("recv user %x online msg", user->ipv4);
            break;
        case PORTAL_TYPE_USER_OFFLINE:
            PORTAL_DEBUG("recv user %x offline msg", user->ipv4);
            break;
        default:
            break;
        }
    } else if (pkt->ver == 0x02) {
        user = portal_find_user(0, &temp_user.ipv6, AF_INET6);
        if (!user) {
            if (pkt->type == PORTAL_TYPE_MAC_INFO_REQ) {
                user = malloc(sizeof(struct portal_user_s));
                if (!user) {
                    return;
                }
                memset(user, 0, sizeof(struct portal_user_s));
                user->authen_mode = pkt->pap_chap;
                memcpy(user->mac, temp_user.mac, 6);
                user->nasip = temp_user.nasip;
                PORTAL_DEBUG("add user %x, nasip %x", user->ipv4, user->nasip);
                memcpy(&user->ipv6, &temp_user.ipv6, sizeof(IPADDRTYPE6));
                list_add_tail(&user->lst, &portal_args.v6_user);
                user->af = AF_INET6;
            } else {
                return;
            }
        }
        switch (pkt->type) {
        case PORTAL_TYPE_ACK_CHALLENGE:
            if (pkt->errcode == 0x01 || pkt->errcode == 0x04) {
                snprintf(send_buf, sizeof(send_buf), portal_result_authen_failed, user->err_id);
                http_fd = portal_get_authen_fd(&user->sock_lst);
                if (http_fd) {
                    send(http_fd, send_buf, strlen(send_buf), 0);
                }
                portal_del_user(user);
                PORTAL_DEBUG("here");
                return;
            } else if (pkt->errcode == 0x03) {
                break;
            }
            memcpy(user->challenge, temp_user.challenge, CHALLENGE_LEN);
            if (!memcmp(user->challenge, challenge, CHALLENGE_LEN)) {
                return;
            }
            user->req_id = ntohs(pkt->reqid);
            portal_send_msg(user, fd, PORTAL_TYPE_REQ_AUTH);
            break;
        case PORTAL_TYPE_ACK_AUTH:
            if (pkt->errcode == 0x01 || pkt->errcode == 0x04) {
                memcpy(user->err_id, temp_user.err_id, sizeof(user->err_id));
                snprintf(send_buf, sizeof(send_buf), portal_result_authen_failed, user->err_id);
                http_fd = portal_get_authen_fd(&user->sock_lst);
                if (http_fd) {
                    send(http_fd, send_buf, strlen(send_buf), 0);
                }
                portal_del_user(user);
                PORTAL_DEBUG("here");
                return;
            } else if (pkt->errcode == 0x03) {
                break;
            }
            portal_send_msg(user, fd, PORTAL_TYPE_AFF_ACK_AUTH);
            if (!user->bind_mac) {
                inet_ntop(AF_INET, &portal_args.portal_ip, ipbuf, sizeof(ipbuf));
                snprintf(send_buf, sizeof(send_buf), portal_result_302, ipbuf, portal_args.http_port, ONLINE, user->sessionid);
                http_fd = portal_get_authen_fd(&user->sock_lst);
                if (http_fd) {
                    send(http_fd, send_buf, strlen(send_buf), 0);
                }
            } else {
                portal_bindmac_upd_userinfo(user);
            }
            break;
        case PORTAL_TYPE_ACK_LOGOUT:
            inet_ntop(AF_INET, &portal_args.portal_ip, ipbuf, sizeof(ipbuf));
            snprintf(send_buf, sizeof(send_buf), portal_result_302, ipbuf, portal_args.http_port, OFFLINE, user->sessionid);
            http_fd = portal_get_authen_fd(&user->sock_lst);
            if (http_fd) {
                send(http_fd, send_buf, strlen(send_buf), 0);
            }
            break;
        case PORTAL_TYPE_NTF_LOGOUT:
            portal_send_msg(user, fd, PORTAL_TYPE_ACK_LOGOUT);
            portal_del_user(user);
            break;
        case PORTAL_TYPE_ACK_INFO:
            break;
        case PORTAL_TYPE_MAC_INFO_REQ:
            if (portal_find_bindmac(user->mac)) {
                user->bind_mac = TRUE;
            }
            user->serialno = ntohs(pkt->serialno);
            PORTAL_DEBUG("shuffle %d, user mac %d", portal_args.portal_mac_sms_shuffle, user->bind_mac);
            if (portal_args.portal_mac_sms_shuffle && user->bind_mac) {
                portal_bindmac_get_userinfo(user);
                portal_send_msg(user, fd, user->authen_mode ? PORTAL_TYPE_REQ_AUTH : PORTAL_TYPE_REQ_CHALLENGE);
                portal_send_msg(user, fd, PORTAL_TYPE_MAC_INFO_ACK);
            } else {
                portal_send_msg(user, fd, PORTAL_TYPE_MAC_INFO_ACK);
                if (user->bind_mac) {
                    portal_bindmac_get_userinfo(user);
                    portal_send_msg(user, fd, user->authen_mode ? PORTAL_TYPE_REQ_AUTH : PORTAL_TYPE_REQ_CHALLENGE);
                }
            }
            break;
        case PORTAL_TYPE_USER_ONLINE:
            break;
        case PORTAL_TYPE_USER_OFFLINE:
            break;
        default:
            break;
        }
    } else {
        return;
    }
}


static void portal_send_pkt(int fd, IPADDRTYPE ip, ushort port, struct portal_pkt_s *pkt, int len)
{
    int send_len;
    struct sockaddr_in sin;

    memset(&sin, 0, sizeof(sin));
    memcpy(&sin.sin_addr, &ip, sizeof(sin.sin_addr));
    sin.sin_port = htons(port);
    send_len = sendto(fd, pkt, len, 0, (struct sockaddr *)&sin, sizeof(sin));
    if (send_len != len) {
        PORTAL_DEBUG("send_len %d, len %d", send_len, len);
    }
}

void portal_pro_auth(int fd)
{
    struct sockaddr_in sin;
    uchar portal_pkt_buf[1024];
    socklen_t len = sizeof(sin);
    int rx_len;
    struct portal_pkt_s *pkt;

    memset(portal_pkt_buf, 0, sizeof(portal_pkt_buf));
    rx_len = recvfrom(fd, portal_pkt_buf, sizeof(portal_pkt_buf), 0, (struct sockaddr *)&sin, &len);
    if (rx_len <= 0) {
        return;
    }

    portal_print_pkt(portal_pkt_buf, rx_len);
    pkt = (struct portal_pkt_s *)portal_pkt_buf;
    portal_parse_pkt(fd, pkt);
}


static void portal_add_ipv6_attr(uchar *buf, uchar *ipv6)
{
    *buf++ = PORTAL_ATTR_USER_IPV6;
    *buf++ = 18;
    memcpy(buf, ipv6, 16);
}

static void portal_add_chapasswd_attr(uchar *buf, ushort req_id, uchar *cha, char *password)
{
    uchar chap_org[64], output[16];

    *buf++ = PORTAL_ATTR_CHAPPASSWORD;
    *buf++ = 18;
    memset(chap_org, 0, sizeof(chap_org));
    chap_org[0] = (uchar)req_id;
    memcpy(chap_org + 1, password, strlen(password));
    memcpy(chap_org + 1 + strlen(password), cha, 16);
    portal_md5(chap_org, 16 + 1 + strlen(password), (char *)output);
    memcpy(buf, output, 16);
}

static void portal_add_username_attr(uchar *buf, char *username, uchar len)
{
    *buf++ = PORTAL_ATTR_USERNAME;
    *buf++ = (uchar)len + 2;
    memcpy(buf, username, len);
}

static void portal_add_password_attr(uchar *buf, char *password, uchar len)
{
    *buf++ = PORTAL_ATTR_PASSWORD;
    *buf++ = len + 2;
    memcpy(buf, password, len);
}

void portal_send_msg(struct portal_user_s *user, int fd, int msg_type)
{
    uchar tx_buf[1024];
    struct portal_pkt_s *pkt;
    int pkt_len = 0;

    memset(tx_buf, 0, sizeof(tx_buf));
    pkt = (struct portal_pkt_s *)tx_buf;
    pkt->ver = user->af == AF_INET ? 1 : 2;
    pkt->pap_chap = user->authen_mode;
    pkt->rsv = 0;
    pkt->userip = user->af ? htonl(user->ipv4) : 0;
    pkt->userport = 0;
    pkt->type = msg_type;
    pkt->reqid = htons(user->req_id);
    pkt->serialno = htons(user->serialno);
    pkt->errcode = 0;

    switch (msg_type) {
    case PORTAL_TYPE_REQ_CHALLENGE:
        user->serialno = (ushort)random();
        pkt->reqid = user->req_id = 0;
        pkt->serialno = htons(user->serialno);
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr((uchar *)pkt->attrs, (uchar *)&user->ipv6);
            pkt->attrnum = 1;
            pkt_len = 18;
        }
        break;
    case PORTAL_TYPE_REQ_AUTH:
        portal_add_username_attr(pkt->attrs, user->username, strlen(user->username));
        pkt_len = strlen(user->username) + 2;
        pkt->attrnum++;
        if (pkt->pap_chap) {
            user->serialno = (ushort)random();
            pkt->serialno = htons(user->serialno);
            portal_add_password_attr(pkt->attrs + pkt_len, user->password, strlen(user->password));
            pkt_len += strlen(user->password) + 2;
            pkt->attrnum++;
        } else {
            portal_add_chapasswd_attr(pkt->attrs + pkt_len, user->req_id, user->challenge, user->password);
            pkt_len += 18;
            pkt->attrnum++;
        }
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr(pkt->attrs + pkt_len, (uchar *)&user->ipv6);
            pkt->attrnum++;
            pkt_len += 18;
        }
        break;
    case PORTAL_TYPE_REQ_INFO:
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr(pkt->attrs + pkt_len, (uchar *)&user->ipv6);
            pkt->attrnum++;
            pkt_len += 18;
        }
        break;
    case PORTAL_TYPE_REQ_LOGOUT:
        user->serialno = (ushort)random();
        pkt->serialno = htons(user->serialno);
        pkt->reqid = 0;
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr(pkt->attrs + pkt_len, (uchar *)&user->ipv6);
            pkt->attrnum++;
            pkt_len += 18;
        }
        break;
    case PORTAL_TYPE_MAC_INFO_ACK:
        if (!user->bind_mac) {
            pkt->errcode = 1;
        }
        break;
    case PORTAL_TYPE_ACK_LOGOUT:
        pkt->reqid = 0;
        pkt->serialno = 0;
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr(pkt->attrs + pkt_len, (uchar *)&user->ipv6);
            pkt->attrnum++;
            pkt_len += 18;
        }
        break;
    case PORTAL_TYPE_AFF_ACK_AUTH:
        if (user->af == AF_INET6) {
            portal_add_ipv6_attr(pkt->attrs + pkt_len, (uchar *)&user->ipv6);
            pkt->attrnum++;
            pkt_len += 18;
        }
        break;
    default:
        return;
    }

    portal_send_pkt(fd, user->nasip, 2000, pkt, pkt_len + sizeof(struct portal_pkt_s));
}

